{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "f8da2924-07bb-4e1e-9372-6ff2685a5312",
   "metadata": {},
   "source": [
    "# Building complex widget libraries"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e31c1960-d4db-4681-8cbf-5d88b82c5e50",
   "metadata": {
    "tags": []
   },
   "source": [
    "## The problem\n",
    "Often when composing widgets into an application, it becomes cumbersome to find a good way of managing state and keeping track of variables as the application grows. This notebooks aims to provide gentle guidance for managing complex application libraries. Please bear in mind these are merely **suggestions** and are by no means the only, or even best, way of going about this."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82d1eef5-52fe-4b37-8c80-cb812cbd4324",
   "metadata": {},
   "source": [
    "### The current approach"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5a7f24bc-4df9-481a-a5c3-380485d5ff35",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Generating data\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "\n",
    "np.random.seed(0)\n",
    "p_t, n = 100, 260\n",
    "stock_df = pd.DataFrame({f'Stock {i}': p_t + np.round(np.random.standard_normal(n).cumsum(), 2) for i in range(10)})"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "488c6461-0e3c-4234-ad4c-f03ef6115996",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Imports\n",
    "from bqplot import LinearScale, Axis, Figure, Lines, CATEGORY10\n",
    "from ipywidgets import HBox, VBox, Layout, HTML\n",
    "from ipydatagrid import DataGrid\n",
    "    \n",
    "# Setting up the data grid\n",
    "stock_grid = DataGrid(stock_df, selection_mode='column')\n",
    "\n",
    "# Creating the bqplot chart objects\n",
    "sc_x = LinearScale()\n",
    "sc_y = LinearScale()\n",
    "line = Lines(x=[], y=[], labels=['Fake stock price'], display_legend=True,\n",
    "                 scales={'x': sc_x, 'y': sc_y})\n",
    "ax_x = Axis(scale=sc_x, label='Index')\n",
    "ax_y = Axis(scale=sc_y, orientation='vertical', label='y-value')\n",
    "fig = Figure(marks=[line], axes=[ax_x, ax_y], title='Line Chart', layout=Layout(flex='1 1 auto', width='100%'))\n",
    "\n",
    "# Creating the application title\n",
    "app_title = HTML(value=\"<h1 style='color: salmon'>My complex application</h1><h2>Select a column to plot it</h2>\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4d84c7b3-451c-4c88-b59e-4d0ae3d578e6",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Define callbacks\n",
    "def plot_stock(*args):\n",
    "    line.y = stock_grid.selected_cell_values\n",
    "    line.x = range(len(line.y))\n",
    "    column_index = stock_grid.selections[0]['c1']\n",
    "    line.labels = [stock_df.columns[column_index]]\n",
    "    line.colors = [CATEGORY10[np.random.randint(0, len(CATEGORY10)) % len(CATEGORY10)]]\n",
    "    \n",
    "# Event listener for cell click\n",
    "stock_grid.observe(plot_stock, names='selections')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79e135e3-b37c-457b-a268-3695a3def263",
   "metadata": {},
   "outputs": [],
   "source": [
    "VBox([\n",
    "    app_title,\n",
    "    HBox(\n",
    "        [stock_grid, fig]\n",
    "    )\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c635ec7-c85e-425b-ab5e-f3e4ad937a37",
   "metadata": {},
   "source": [
    "### A more structured approach"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47f6bfa5-b044-4da2-bac8-d3066ac1532d",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Chart:\n",
    "    def __init__(self, figure_title='Line Chart'):\n",
    "        self._sc_x = LinearScale()\n",
    "        self._sc_y = LinearScale()\n",
    "        self._line = Lines(x=[], y=[], labels=['Fake stock price'], display_legend=True,\n",
    "                         scales={'x': self._sc_x, 'y': self._sc_y})\n",
    "        self._ax_x = Axis(scale=self._sc_x, label='Index')\n",
    "        self._ax_y = Axis(scale=self._sc_y, orientation='vertical', label='y-value')\n",
    "        self._fig = Figure(marks=[self._line], axes=[self._ax_x, self._ax_y], title=figure_title, layout=Layout(flex='1 1 auto', width='100%'))\n",
    "        \n",
    "    def get_figure(self):\n",
    "        return self._fig\n",
    "    \n",
    "    def get_line(self):\n",
    "        return self._line\n",
    "    \n",
    "    def set_line(self, x, y, labels, colors):\n",
    "        self._line.x = x\n",
    "        self._line.y = y\n",
    "        self._line.labels = labels\n",
    "        self._line.colors = colors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "edaf116c-d873-4a24-ba8c-9536de90e4cc",
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import display\n",
    "\n",
    "class MyApplication:\n",
    "    def __init__(self, data, application_title='My complex application'):\n",
    "        self.dataframe = data\n",
    "        self.datagrid = self.process_data(data)\n",
    "        self.chart = Chart()\n",
    "        self.app_title = HTML(value=f\"<h1 style='color: salmon'>{application_title}</h1><h2>Select a column to plot it</h2>\")\n",
    "        self.run_application()\n",
    "        \n",
    "    def process_data(self, dataframe):\n",
    "        return DataGrid(dataframe, selection_mode='column')\n",
    "        \n",
    "    def generate_layout(self):\n",
    "        return VBox([self.app_title, HBox([self.datagrid, self.chart.get_figure()])])\n",
    "    \n",
    "    def setup_event_handlers(self):\n",
    "        self.datagrid.observe(self.plot_stock, names='selections')\n",
    "    \n",
    "    def run_application(self):\n",
    "        self.setup_event_handlers()\n",
    "        display(self.generate_layout())\n",
    "        \n",
    "    # Callbacks section\n",
    "    def plot_stock(self, *args):\n",
    "        column_index = self.datagrid.selections[0]['c1']\n",
    "        line = self.chart.get_line()\n",
    "        selected_values = self.datagrid.selected_cell_values\n",
    "        self.chart.set_line(\n",
    "            range(len(selected_values)), \n",
    "            selected_values, \n",
    "            [self.dataframe.columns[column_index]], \n",
    "            [CATEGORY10[np.random.randint(0, len(CATEGORY10)) % len(CATEGORY10)]]\n",
    "        )"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "05fdd2fa-ecac-4b1e-add4-362a8814f491",
   "metadata": {},
   "outputs": [],
   "source": [
    "app = MyApplication(data=stock_df, application_title=\"An alternative approach\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "widgets-tutorial",
   "language": "python",
   "name": "widgets-tutorial"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
